<?php
/**
 * Memcache列表方案
 * 暂时可能混合的特性比较多，以后再慢慢拆出来
 * 这些特性包括：
 * ok数据同时存储在Mysql和Memcache中
 * ok读取会先读MEMCACHE，如果没有再从MYSQL里读
 * ok不支持删除
 * ok插入时先在MEMCACHE中生成ID
 * ok可初始化CACHE中的ID
 * ok支持按页读取（倒序读取），顺序读取
 * 能支持分布
 *
 * @author ananda
 */
abstract class Pattern_Memcache_List extends Pattern_Memcache
{
    /**
     * 传统数据库连接
     * !require!
     * @var <Connection_Pdo>
     */
    public $conn_db = null;

    /**
     * 数据库中的表名
     * !require!
     * @var <string>
     */
    public $table_name   = '';

    /**
     * 主键ID名
     * @var <string>
     */
    public $primary_name = 'id';

    /**
     * 数据库中添加记录的SQL
     * !require!
     * @var <string>
     */
    public $sql_append   = '';

    /**
     * 数据库中读取记录的SQL
     * !require!
     * @var <string>
     */
    public $sql_read     = '';

    /**
     * 数据库中取得最新ID值的SQL
     * !require!
     * @var <string>
     */
    public $sql_renew_id = '';

    /**
     * 在缓存中的序列号生成变量KEY值
     * @var <string>
     */
    public $prefix_seq   = 'table_seq:';

    /**
     * 缓存前缀
     * @var <string>
     */
    public $prefix_cache = 'table:';

    /**
     * 添加一条记录
     * @param <array> $row
     * @return boolean
     */
    public function append($row)
    {
        // 生成ID和KEY值
        $id  = $this->getInsertId();
        if (Error::is($id)) {
            return $id;
        }
        $key = $this->generateKey($id);
        $row = array_merge(array($this->primary_name => $id), $row);

        // 往数据库中插入数据
        $result = $this->getDbConnection()->create($this->sql_append, $row);
        if (Error::is($result)) {
            return $result;
        }

        // 成功的话，继续往缓存中插入数据
        $result = $this->getCacheConnection()->create($key, $row);
        if (Error::is($result)) {
            return $result;
        }
        return $id;
    }

    /**
     * 获取一系列的缓存记录
     * @param <array> $key_list
     * @param <array> $id_list
     * @param <enum> $id_list
     * @return Data_Collection
     */
    public function get($key_list, $id_list, $sort)
    {
        // 防止没有数据的情况发生
        if (!count($key_list)) {
            return array();
        }

        $it = $this->getCacheConnection()->read($key_list);

        if (Error::is($it) || $it->rowCount() != count($key_list)) {
            // 数据不存在，重建
            $id_list = '"'.implode('","', $id_list).'"';

            // 读取MYSQL中的数据
            $db  = $this->getDbConnection();
            $sql = $db->replaceParams($this->sql_read, array('id_list' => $id_list));
            if ($sort ==Pattern_Memcache::ORDER_DESC) {
                $sql .= " ORDER BY $this->primary_name DESC";
            } else {
                $sql .= " ORDER BY $this->primary_name ASC";
            }
            $it = $db->read($sql);
            if (Error::is($it)) {
                return $it;
            }

            // 创建迭代器
            $it = new Pattern_Memcache_RebuildCache($it, $this, 'primary_name');

            /** 以下这段是不使用迭代器低效率的展示
            $collection = array();
            foreach ($it as $row) {
                $collection[] = $row;
                $conn->update($this->generateKey($row[$this->primary_name]), $row);
            }
            $it = $conn->buildResult('Data_Collection_Memcache', $collection);
             */
        }
        return $it;
    }

    /**
     * 根据一个ID列表读取一系列记录
     * @param array $id_list
     * @return Data_Collection
     */
    public function getList($id_list)
    {
        $key_list = array();
        foreach ($id_list as $id) {
            $key_list[] = $this->generateKey($id);
        }
        return $this->get($key_list, $id_list, Pattern_Memcache::ORDER_DESC);
    }

    /**
     * 根据ID读取一条记录
     * @param <integer> $id
     * @return <array>
     */
    public function getOne($id)
    {
        $key = $this->generateKey($id);
        $it  = $this->get(array($key), array($id), Pattern_Memcache::ORDER_DESC);
        if (Error::is($it)) {
            return $it;
        }
        return $it->rowCount() ? $it->current() : false;
    }

    /**
     * 排序显示列表
     * 按当前页，每页限制条数来显示
     * @param <integer> $page
     * @param <integer> $limit
     * @param <enum> $sort
     */
    public function order($page = 1, $limit = 20, $sort = Pattern_Memcache::ORDER_DESC)
    {
        // 算出ID列表
        $page    = max($page, 1);
        $last_id = $this->lastInsertId();
        $list    = $this->getListId($last_id, $page, $limit, $sort);

        // 取数据
        return $this->get($list['key'], $list['id'], $sort);
    }

    /**
     * 重建ID值
     * @return <integer>
     */
    public function renewId()
    {
        $row = $this->getDbConnection()->readOne($this->sql_renew_id, array(),
                                                 array('fetch_mode' => PDO::FETCH_BOTH));
        if (Error::is($row)) {
            return $row;
        }
        $id  = intval(max($row[0], 0));

        $result = $this->getCacheConnection()->update($this->sequenceKey(), $id);
        if (Error::is($result)) {
            return $result;
        }

        return $id;
    }

    /**
     * 生成在MEMCACHE中存入的键值
     * @param <integer> $id
     * @return <string>
     */
    public function generateKey($id)
    {
        return $this->prefix_cache.$this->table_name.'/'.$id;
    }

    /**
     * 获取序列号ID
     * @return <type>
     */
    public function sequenceKey()
    {
        return $this->prefix_seq.$this->table_name;
    }

    /**
     * 取得最后插入的记录ID
     * @return <integer>
     */
    public function lastInsertId()
    {
        $id = $this->getCacheConnection()->readOne($this->sequenceKey());
        if (Error::is($id)) {
            $result = $this->renewId();
            if (Error::is($result)) {
                return $result;
            }
            $id = $this->getCacheConnection()->readOne($this->sequenceKey());
            if (Error::is($id)) {
                return $id;
            }
        }
        return intval($id);
    }

    /**
     * 取得要插入的记录ID
     * @return <integer>
     */
    protected function getInsertId()
    {
        $id = $this->getCacheConnection()->increment($this->sequenceKey());
        if (Error::is($id) || !$id) {
            $result = $this->renewId();
            if (Error::is($result)) {
                return $result;
            }
            $id = $this->getCacheConnection()->increment($this->sequenceKey());
            if (Error::is($id)) {
                return $id;
            }
        }
        return intval($id);
    }

    /**
     * 获取数据库连接对象
     * @return Connection_Pdo
     */
    public function getDbConnection()
    {
        if (!is_object($this->conn_db)) {
            $this->conn_db = Connection::get($this->conn_db);
        }
        return $this->conn_db;
    }

}
